package com.wh1200.mybatis.xmlless.resolver

import com.wh1200.kotlin.only
import com.wh1200.kotlin.toCamelCase
import com.wh1200.kotlin.toWords
import com.wh1200.mybatis.xmlless.AnnotatedElementDetailInfo
import com.wh1200.mybatis.xmlless.enums.Operations
import com.wh1200.mybatis.xmlless.kotlin.split
import com.wh1200.mybatis.xmlless.model.*
import com.wh1200.mybatis.xmlless.model.base.BaseCriteria
import com.wh1200.mybatis.xmlless.model.base.IExpressionToken
import com.wh1200.mybatis.xmlless.model.component.ConditionExpression
import com.wh1200.mybatis.xmlless.model.component.ExpressionGroup
import com.wh1200.mybatis.xmlless.model.component.StringExpression
import com.wh1200.mybatis.xmlless.model.criteria.CriteriaParamName
import com.wh1200.mybatis.xmlless.model.criteria.CriteriaParameter
import com.wh1200.mybatis.xmlless.model.expression.*
import com.wh1200.mybatis.xmlless.util.operationsNameWords
import net.sf.jsqlparser.expression.BinaryExpression
import net.sf.jsqlparser.expression.Expression
import net.sf.jsqlparser.expression.operators.relational.*
import net.sf.jsqlparser.parser.CCJSqlParser
import net.sf.jsqlparser.parser.CCJSqlParserUtil
import net.sf.jsqlparser.schema.Column

/**
 *
 * Created by 吴昊 on 2018-12-15.
 *
 * @author 吴昊
 * @since 0.0.8
 */
object CriteriaResolver {

  /** 所有的操作符 */
  private val OPERATORS = Operations.values().map {
    listOf(
        it.operator,
        it.operator.lowercase(),
        it.expression,
        it.expression.lowercase()
    )
  }.flatten().filter { it.isNotBlank() }.sortedDescending()

  private fun getCriteria(info: AnnotatedElementDetailInfo, methodInfo: MethodInfo): List<QueryCriteria> {
    val result = mutableListOf<QueryCriteria>()
    val criteria = info.criteria
    if (criteria != null) {
      val properties = if (criteria.properties.isNotEmpty()) {
        criteria.properties.toList()
      } else {
        listOf(info.name)
      }
      val paramName = chooseFromParameter(methodInfo, info.name, ValueHolder(0))
      result.addAll(
          properties.map {
            QueryCriteria(
                it, criteria.value, listOf(paramName), methodInfo
            )
          }
      )
    }
    return result.toList()
  }

  fun getOtherWiseCriteria(field: AnnotatedElementDetailInfo, methodInfo: MethodInfo): BaseCriteria? {
    if (field.chooseCriteria.otherwise.isBlank()) {
      return null
    }
    return resolveConditionsFromExpression(
        field.chooseCriteria.otherwise.trim(), methodInfo
    )
  }

  fun getTestCriteriaInfo(field: AnnotatedElementDetailInfo, methodInfo: MethodInfo): List<WhenCriteria> {
    return field.chooseCriteria.whenList.map { criteria ->
      val group = resolveConditionsFromExpression(
          criteria.sqlExpression.trim(), methodInfo
      )
      if (group is QueryCriteriaGroup) {
        WhenCriteria(group.criterion, methodInfo, TestInfo(criteria.ifExpression, field))
      } else {
        WhenCriteria(listOf(group), methodInfo, TestInfo(criteria.ifExpression, field))
      }
    }
  }

  fun resolveConditionsFromExpression(
      expression: String,
      methodInfo: MethodInfo
  ): BaseCriteria {
    val stringExp = parseExpression(expression)
    if (stringExp is ExpressionGroup) {
      return resolveGroupCondition(stringExp, methodInfo)
    }
    val exp = CCJSqlParser(expression).Expression()
    if (exp is Column && exp.toString() == expression) {
      return resolveConditionsFromExpression("$exp = $exp", methodInfo)
    }
    if (exp is Between
        || exp is IsNullExpression
        || exp is LikeExpression
        || (exp is InExpression && exp.rightItemsList != null) || exp is ComparisonOperator
    ) {
      return parseExpressionCriteria(exp, methodInfo)
    }
    if (stringExp !is StringExpression) {
      error("无法解析的表达式【$expression】")
    }
    if (stringExp.tokens.none { it is Operator }) {
      error("无法解析的表达式【$expression】")
    }
    val operator = stringExp.tokens.first { it is Operator }
    val tokensBeforeOperator = stringExp.tokens.take(stringExp.tokens.indexOf(operator))
    val tokensAfterOperator = stringExp.tokens.drop(stringExp.tokens.indexOf(operator) + 2)
        .filter { it !is Whitespace }
    val propertyToken = tokensBeforeOperator.first { it is Token }
    if (stringExp.tokens.count { it is Operator } > 1) {
      if (operator.toSql().lowercase() == "between" && tokensAfterOperator.size == 3) {
        val parameters = listOf(
            chooseFromParameter(
                methodInfo, tokensAfterOperator[0].toSql(), ValueHolder(0)
            ),
            chooseFromParameter(
                methodInfo, tokensAfterOperator[2].toSql(), ValueHolder(0)
            )
        )
        return QueryCriteria(
            propertyToken.toSql(), Operations.Between, parameters,
            methodInfo,
            null,
            ConditionConnector.OR
        )
      }
      error("无法解析的表达式【$expression】")
    }
    if (tokensBeforeOperator.count { it is Token } != 1) {
      error("无法解析的表达式【$expression】")
    }
    if (tokensAfterOperator.none { it is Token || it.isValue() }) {
      return resolveConditionsFromExpression(
          expression + " " + propertyToken.toSql(),
          methodInfo
      )
    }
    val map = mapOf(
        "in" to Operations.In,
        "not in" to Operations.NotIn,
        "contains" to Operations.Contains,
        "contains any" to Operations.ContainsAny,
        ">=" to Operations.Gte,
        "<=" to Operations.Lte,
        ">" to Operations.Gt,
        "<" to Operations.Lt,
        "!=" to Operations.Ne
    )
    val op = map[operator.toSql().lowercase()] ?: error("无法解析的表达式【$expression】")
    if (tokensAfterOperator.count { it is Token || it.isValue() } != 1) {
      error("无法解析的表达式【$expression】")
    }
    val parameterToken = tokensAfterOperator.first { it is Token || it.isValue() }
    return if (parameterToken.isValue()) {
      QueryCriteria(
          propertyToken.toSql(), op, listOf(),
          methodInfo,
          when (parameterToken) {
            is StringValue -> SpecificValue(parameterToken.toSql())
            else           -> SpecificValue(nonStringValue = parameterToken.toSql())
          },
          ConditionConnector.AND
      )
    } else {
      val parameter = chooseFromParameter(
          methodInfo, parameterToken.toSql(), ValueHolder(0)
      )
      QueryCriteria(
          propertyToken.toSql(), op, listOf(parameter),
          methodInfo,
          null,
          ConditionConnector.AND
      )
    }
  }

  fun resolveConditions(
      expression: String,
      methodInfo: MethodInfo
  ): BaseCriteria {
    val list = resolveConditions(expression.toWords(), methodInfo)
    if (list.size == 1) {
      return list.only()
    }
    return QueryCriteriaGroup(list, methodInfo)
  }

  fun resolveConditions(
      allConditionWords: List<String>,
      methodInfo: MethodInfo
  ): List<BaseCriteria> {
    return if (allConditionWords.isNotEmpty()) {
      val parameterOffsetHolder = ValueHolder(0)
      splitAndConditionKeywords(allConditionWords).map { addPropertiesWords ->
        // 解析形如 nameEq 或者 nameLikeKeywords 的表达式
        // nameEq 解析为 name = #{name}
        // nameLikeKeywords 解析为 name  LIKE concat('%',#{keywords},'%')
        addPropertiesWords.split("Or").map { singleConditionWords ->
          resolveCriteria(singleConditionWords, methodInfo, parameterOffsetHolder)
        }.apply {
          last().append = ConditionConnector.AND
        }
      }.flatten()
    } else {
      listOf<QueryCriteria>()
    }
  }

  fun createComplexParameterCondition(methodInfo: MethodInfo): List<List<BaseCriteria>> {
    val parameterConditions = arrayListOf<List<BaseCriteria>>()
    val paramNames = methodInfo.paramNames
    methodInfo.parameters.forEachIndexed { index, parameter ->
      if (parameter.isComplex) {
        (parameter.realType ?: parameter.type).fields.forEach { field ->
          if (field.chooseCriteria.whenList.isNotEmpty()) {
            parameterConditions.add(
                listOf(
                    ChooseCriteria(
                        field.name,
                        listOf(
                            resolveCriteriaParameterFromProperty(
                                field.name,
                                field,
                                paramNames[index]
                            )
                        ),
                        methodInfo
                    )
                )
            )
          }
          parameterConditions.add(getCriteria(field, methodInfo))
        }
      }
    }
    return parameterConditions.filter { it.isNotEmpty() }
  }

  fun chooseFromParameter(
      methodInfo: MethodInfo,
      property: String,
      parameterOffsetHolder: ValueHolder<Int>
  ): CriteriaParameter {
    if (property.matches("\\d+".toRegex()) || property in listOf("true", "false")) {
      return ParameterResolver.buildCriteriaParameter(
          CriteriaParamName(property),
          null, true
      )
    }
    val optionalParam = methodInfo.findOptionalParam(property)
    return if (optionalParam != null) {
      ParameterResolver.buildCriteriaParameter(
          CriteriaParamName(optionalParam.name(), property),
          optionalParam.element, false
      )
    } else {
      if (parameterOffsetHolder.value > methodInfo.parameters.size - 1) {
        error("无法从方法参数中解析出【${property}】所需参数名称")
      }
      val el = methodInfo.parameters[parameterOffsetHolder.value].parameter
      parameterOffsetHolder.value++
      ParameterResolver.buildCriteriaParameter(
          CriteriaParamName(el.paramName, property),
          el, false
      )
    }
  }

  fun resolveCriteria(
      singleConditionWords: List<String>,
      methodInfo: MethodInfo,
      parameterOffsetHolder: ValueHolder<Int>
  ): QueryCriteria {
    // 获取表示条件表达式操作符的单词
    val singleConditionString = singleConditionWords.joinToString("").toCamelCase()
    val opWordsList = operationsNameWords().filter {
      singleConditionWords.containsAll(it.words)
          && singleConditionWords.joinToString("").contains(it.words.joinToString(""))
    }.sortedByDescending { it.words.size }
    val maxOpWordCount = opWordsList.maxOfOrNull { it.words.size }
    val opWords = opWordsList.filter { it.words.size == maxOpWordCount }
        .minByOrNull {
          singleConditionString.indexOf(it.words.joinToString(""))
        }
    // 解析条件表达式的二元操作符 = > < >= <= != in like 等
    val op: Operations = when {
      opWords != null -> opWords.op
      else            -> Operations.Eq
    }
    val props = when {
      opWords != null -> singleConditionWords.split(opWords.words).map { it.split("") }.flatten()
      else            -> listOf(singleConditionWords)
    }.toMutableList()

    check(props.size in 1..3) { "无法从${singleConditionWords.joinToString("")}中解析查询条件" }
    val str = props.first().joinToString("")
    val property = if (str.contains(".")) {
      str.split(".").joinToString(".") { it.toCamelCase() }
    } else {
      str.toCamelCase()
    }
    val paramNames = getParamNames(props, property, op, methodInfo, parameterOffsetHolder)
    val parameters = if (op.parameterCount != 0) {
      paramNames
    } else {
      listOf()
    }
    return QueryCriteria(
        property, op, parameters,
        methodInfo,
        null,
        ConditionConnector.OR
    )
  }

  fun resolveCriteriaParameterFromProperty(
      name: String,
      property: AnnotatedElementDetailInfo,
      paramName: String?
  ): CriteriaParameter {
    return ParameterResolver.buildCriteriaParameter(
        CriteriaParamName(
            if (paramName.isNullOrBlank()) {
              name
            } else {
              "$paramName.$name"
            }, name
        ), property
    )
  }

  fun parseExpression(str: String): ConditionExpression {
    val exp = ExpressionResolver.parseExpression(str)
    return toExpression(exp.getNodes())
  }

  private fun resolveGroupCondition(group: ExpressionGroup, methodInfo: MethodInfo): BaseCriteria {
    return QueryCriteriaGroup(
        group.expressions.mapIndexed { index, it ->
          val append = if (index == group.expressions.size - 1) {
            ConditionConnector.AND
          } else {
            group.connector
          }
          when (it) {
            is StringExpression -> resolveConditionsFromExpression(it.expression.trim(), methodInfo).clone(append)
            is ExpressionGroup  -> resolveGroupCondition(it, methodInfo).clone(append)
            else                -> error("无法解析的表达式${it}")
          }
        }, methodInfo, append = ConditionConnector.AND
    )
  }

  private fun parseExpressionCriteria(
      exp: Expression, methodInfo: MethodInfo,
  ): ExpressionCriteria {
    val preHandledExp = preHandleExpression(exp)
    val columns = when (preHandledExp) {
      is BinaryExpression -> preHandledExp.rightExpression.getColumns()
      is Between          -> listOf(
          preHandledExp.betweenExpressionStart,
          preHandledExp.betweenExpressionEnd
      ).map { it.getColumns() }
          .flatten()

      is IsNullExpression,
      is InExpression     -> listOf()
      else                -> error("无法解析的表达式${preHandledExp}")
    }
    val parameters = columns.map {
      chooseFromParameter(methodInfo, it.columnName, ValueHolder(0))
    }
    return ExpressionCriteria(preHandledExp, parameters, methodInfo, ConditionConnector.AND)
  }

  private fun preHandleExpression(exp: Expression): Expression {
    if (exp is LikeExpression && exp.rightExpression is Column) {
      val retval =
          exp.leftExpression.toString() + " " + (if (exp.isNot) "NOT " else "") + exp.stringExpression + " " +
              "CONCAT('%', ${exp.rightExpression.toString()}, '%')"
      return CCJSqlParserUtil.parseExpression(retval)
    }
    return exp
  }

  private fun toExpression(nodes: List<IExpressionToken>): ConditionExpression {
    val exp = ExpressionGroup()
    var leftBrace: LeftBrace? = null
    val subNodes: MutableList<IExpressionToken> = mutableListOf()
    var prevNode: IExpressionToken? = null
    nodes.forEach {
      when {
        it is LeftBrace && prevNode !is FunctionName
            && prevNode?.toSql()?.lowercase() !in listOf("in", "not in")
            && leftBrace == null                         -> {
          if (isNotEmpty(subNodes)) {
            exp.addExpression(toExpression(subNodes))
            subNodes.clear()
          }
          leftBrace = it
        }

        it is RightBrace && it === leftBrace?.rightBrace -> {
          if (isNotEmpty(subNodes)) {
            exp.addExpression(toExpression(subNodes))
            subNodes.clear()
            leftBrace = null
          }
        }

        it is Connector && leftBrace == null             -> {
          if (isNotEmpty(subNodes)) {
            exp.addExpression(toExpression(subNodes))
            subNodes.clear()
          }
          exp.connector = it.connector
        }

        else                                             -> {
          subNodes.add(it)
        }
      }
      if (it !is Whitespace) {
        prevNode = it
      }
    }
    if (isNotEmpty(subNodes)) {
      exp.addExpression(StringExpression(subNodes.joinToString("") { it.toSql() }, subNodes))
    }
    if (exp.expressions.size == 1) {
      return exp.expressions.only()
    }
    return exp
  }

  private fun isNotEmpty(subNodes: MutableList<IExpressionToken>): Boolean {
    return subNodes.any { it !is Whitespace && it !is Connector }
  }

  /**
   * 获取条件内所有参数名称（包含条件字段）
   */
  private fun getParamNames(
      props: List<List<String>>,
      property: String,
      op: Operations?,
      methodInfo: MethodInfo,
      parameterOffsetHolder: ValueHolder<Int>
  ): List<CriteriaParameter> {
    val paramCount = op?.parameterCount ?: 0

    return when (paramCount) {
      2    -> {
        listOf(
            if (props.size > 1) {
              chooseFromParameter(methodInfo, props[1].joinToString("").toCamelCase(), parameterOffsetHolder)
            } else {
              chooseFromParameter(methodInfo, property, parameterOffsetHolder)
            },
            if (props.size > 2) {
              chooseFromParameter(methodInfo, props[2].joinToString("").toCamelCase(), parameterOffsetHolder)
            } else {
              chooseFromParameter(methodInfo, property, parameterOffsetHolder)
            }
        )
      }

      1    -> {
        listOf(
            if (props.size > 1) {
              // 解决剩余的名称为aInB的情况
              val parts = props[1].split("In")
              chooseFromParameter(
                  methodInfo,
                  if (parts.size == 2) {
                    parts[1].joinToString("").toCamelCase()
                  } else {
                    if (props[1].size == 1 && props[1][0].contains(".")) {
                      props[1][0].split(".").mapIndexed { index, s ->
                        if (index == 0) {
                          s.toCamelCase()
                        } else {
                          s
                        }
                      }.joinToString(".")
                    } else {
                      props[1].joinToString("").toCamelCase()
                    }
                  },
                  parameterOffsetHolder
              )
            } else {
              chooseFromParameter(methodInfo, property, parameterOffsetHolder)
            }
        )
      }

      else -> listOf(
          ParameterResolver.buildCriteriaParameter(
              CriteriaParamName(property), null, false
          )
      )
    }
  }

  private fun splitAndConditionKeywords(allConditionWords: List<String>): List<List<String>> {
    val list = arrayListOf<List<String>>()
    allConditionWords.split("And").forEach {
      if (list.isNotEmpty() && list.last().contains(Operations.Between.name)) {
        list[list.size - 1] = list.last() + "" + it
      } else {
        list.add(it)
      }
    }
    return list
  }

}

/**
 * @author 吴昊
 * @date 2024/01/15
 * @version 1.0
 * @since v4.0.0
 */
class ValueHolder<T>(var value: T)
